﻿/*==========================================================================
 * Project: SpriterRuntime
 * File: SpriterCharacter.cs
 * Copyright(C) 2012 Mark Schmelzenbach
 *
 * Spriter character logic
 *
 *==========================================================================
 * Author:
 *    Mark Schmelzenbach <schmelze@gmail.com>
 *==========================================================================*/

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using System.Linq;
namespace SpriterRuntime
{
    /// <summary>
    /// Logic for Spriter character
    ///
    /// This class is responsible for updating and drawing each spriter character
    ///
    /// </summary>
    public class SpriterCharacter
    {


        SpriteFont guiFont;
        // current animation sequence being played
        int currentSequence = 0;
        int currentEntity = 0;
        // current index into animation frames
        int currentFrameIdx = 0;

        // current frame definition being displayed
        int currentFrame = 0;

        // how long to hold the current frame
        System.TimeSpan deltaTime;

        // time current frame has been shown, so far
        System.TimeSpan tic;
        ContentManager Content;
        // animation data
        SpriterData spriterData;

        public bool FlipX;
        public bool FlipY;
        Texture2D bonetext;
        /// <summary>
        /// Construct a new spriter character
        /// </summary>
        /// <param name="animationData">Character animation data, loaded from a SCML file</param>
        public SpriterCharacter(SpriterData animationData, ContentManager cont, Texture2D bonetex)
        {
            bonetext = bonetex;
            Content = cont;

            guiFont = Content.Load<SpriteFont>("gamefont");
            this.spriterData = animationData;
            CurrentSequence = 0;
        }

        /// <summary>
        /// current frame definition being displayed
        /// </summary>
        public int CurrentFrame
        {
            get { return currentFrame; }
        }

        /// <summary>
        /// current animation sequence being displayed
        /// </summary>
        public int CurrentSequence
        {
            get { return currentSequence; }
            set
            {
                if (value < SequenceCount)
                {
                    currentSequence = value;
                    currentFrameIdx = 0;
                    // reset elapsed time
                    tic = new System.TimeSpan(0);
                    // get the initial frame duration
                    deltaTime = new System.TimeSpan(0, 0, 0, 0, (int)spriterData.entities[currentEntity].animations[currentSequence].length);
                    // get the initial frame definition
                    currentFrame = 0;// spriterData.animations[currentSequence].GetFrameIdx(0);
                }
            }
        }

        /// <summary>
        /// Character position
        /// </summary>
        public Vector2 Position
        {
            get;
            set;
        }

        /// <summary>
        /// Character Rotation
        /// </summary>
        public float Rotation
        {
            get;
            set;
        }
        /// <summary>
        /// Character name from the SCML file
        /// </summary>
        public string Name
        {
            get { return spriterData.entities[currentEntity].EntityName; }
        }

        /// <summary>
        /// Current animation name
        /// </summary>
        public string CurrentSequenceName
        {
            get
            {
                return spriterData.entities[currentEntity].animations[currentSequence].AnimationName;
            }
        }

        /// <summary>
        /// Number of animation sequences
        /// </summary>
        public int SequenceCount
        {
            get
            {
                return spriterData.entities[currentEntity].animations.Count;
            }
        }
        private int animationTime = 0;
        /// <summary>
        /// Update the character's animation frames
        /// </summary>
        /// <param name="time">gametime since the last call to update</param>
        public void Update(GameTime time)
        {
            //if (tic > deltaTime)
            //{
            // if enough time has passed, update current frame information
            // technically, this is being calculated incorrectly, we should be updating tic more carefully,
            // and allowing for multiple frames to pass during a single update
            // however, at 60fps and typical animation rates, this is fine for now
            animationTime += (int)time.ElapsedGameTime.TotalMilliseconds;
            //tic = new System.TimeSpan(0);
            //currentFrameIdx++;
            //if (currentFrameIdx == spriterData.entities[currentEntity].animations[currentSequence].length)
            //{
            //    currentFrameIdx = 0;
            //}
            if (animationTime > spriterData.entities[currentEntity].animations[currentSequence].length)
            {
                animationTime = animationTime % spriterData.entities[currentEntity].animations[currentSequence].length;
            }
            Key1 = spriterData.entities[currentEntity].animations[currentSequence].mainline.LastOrDefault(x => x.time <= animationTime);//.Max(x => x.time);//(x=> x.time <= animationTime && x.time <= animationTim)
            //Key2 = spriterData.entities[currentEntity].animations[currentSequence].mainline.FirstOrDefault(x => x.time >= animationTime);//.Max(x => x.time);//(x=> x.time <= animationTime && x.time <= animationTim)

            //}
            // tic += time.ElapsedGameTime;
        }
        SpriterKeyFrame Key1;
        SpriterKeyFrame Key2;

        //Vector2 tmpPosition;
        //Vector2 tmpOrigin;
        //float tmpRotation;
        SpriteEffects effects;


        void PrecalculateBones(SpriteBatch spriteBatch, Vector2? parent_tween_size, float? parent_tween_angle, Vector2? parent_tween_position, int? parentid)
        {
            if (Key1 == null)
            {
                return;
            }
            foreach (var boneref in Key1.bone_refs.Where(x => (parentid == null ? !x.ParentIdSpecified : x.ParentIdSpecified && x.ParentId == parentid)))
            {

                int k1_t;
                int k2_t;
                float k1_x, k1_y, k1_w, k1_h, k1_a, k1_o, k1_origin_x, k1_origin_y;
                float k2_x, k2_y, k2_w, k2_h, k2_a, k2_o, k2_origin_x, k2_origin_y;
                bool k1_s, k2_s;

                var bone_tl = spriterData.entities[currentEntity].animations[currentSequence].Timelines.Single(x => x.TimelineId == boneref.TimelineId);
                var bonekey1 = bone_tl.keys.LastOrDefault(x => x.KeyId <= boneref.KeyId);




                k1_t = bonekey1.time;
                k1_x = bonekey1.KeyBone.Position.X;
                k1_y = bonekey1.KeyBone.Position.Y;
                k1_w = bonekey1.KeyBone.scale.X;
                k1_h = bonekey1.KeyBone.scale.Y;
                k1_a = DegreeToRadian(bonekey1.KeyBone.angle);
                k1_o = bonekey1.KeyBone.opacity;

                k1_s = (bonekey1.spin == 0 ? true : bonekey1.spin == 1 ? true : false);


                var bonekey2 = bone_tl.keys.LastOrDefault(x => x.KeyId <= boneref.KeyId + 1);
                if (bonekey2 == null)
                {
                    if (bone_tl.keys.Last() == bonekey2)
                    {
                        bonekey2 = new SpriterTimelineKey();
                        bonekey2.KeyId = bonekey1.KeyId + 1;
                        bonekey2.KeyBone = bonekey1.KeyBone;
                        bonekey2.spin = bonekey1.spin;
                        bonekey2.time = spriterData.entities[currentEntity].animations[currentSequence].length;
                    }
                    else
                    {
                        throw new Exception("wtf");
                    }
                }


                k2_t = bonekey2.time;
                k2_x = bonekey2.KeyBone.Position.X;
                k2_y = bonekey2.KeyBone.Position.Y;
                k2_w = bonekey2.KeyBone.scale.X;
                k2_h = bonekey2.KeyBone.scale.Y;
                k2_a = DegreeToRadian(bonekey2.KeyBone.angle);
                k2_o = bonekey2.KeyBone.opacity;
                k2_s = (bonekey2.spin == 0 ? true : bonekey2.spin == 1 ? true : false);




                float bone_time = ((float)(animationTime - k1_t) / ((k2_t - k1_t) == 0 ? 1 : (k2_t - k1_t)));
                float bone_tween_x = k1_x + ((k2_x - k1_x) * bone_time);
                float bone_tween_y = k1_y + ((k2_y - k1_y) * bone_time);
                var bone_angle1 = k1_a;
                var bone_angle2 = k2_a;

                //If spin=”1” (counter-clockwise) and angleB - angleA is negative, then add 360 to
                //angleB before using it in the above formula.
                //If spin=”-1”, and angleB - angleA is positive, then subtract 360 from angleB
                //before using it in the above formula.
                if (k1_s && (bone_angle2 - bone_angle1 < 0))
                {
                    bone_angle2 += DegreeToRadian(360);
                }

                if (!k1_s && (bone_angle2 - bone_angle1 > 0))
                {
                    bone_angle2 -= DegreeToRadian(360);
                }
                Vector2 bone_tween_origin = new Vector2(0, 0);
                float bone_tween_angle = bone_angle1 + ((bone_angle2 - bone_angle1) * bone_time);
                float bone_tween_wdt = k1_w + ((k2_w - k1_w) * bone_time);
                float bone_tween_hgt = k1_h + ((k2_h - k1_h) * bone_time);
                Vector2 bone_tween_size = new Vector2(bone_tween_wdt, bone_tween_hgt);
                Vector2 bone_tween_position = new Vector2(bone_tween_x, bone_tween_y);


                Matrix tmpmatrix = Matrix.Identity;
                if (!boneref.ParentIdSpecified)
                {
                    //boneref.Transform = //Matrix.CreateTranslation(-bone_tween_position.X, -bone_tween_position.Y, 0f) *
                    //      Matrix.CreateScale(bone_tween_size.X, bone_tween_size.Y, 0f) *
                    //       Matrix.CreateRotationZ(bone_tween_angle) *
                    //       Matrix.CreateTranslation(bone_tween_position.X, bone_tween_position.Y, 0f);
                    //tmpmatrix = boneref.Transform;

                }
                else
                {

                    //Usually it is scale * rotation * translation. However, if you want to rotate an object around 
                    //a certain point, then it is scale * point_translation * rotation * object_translation.
                    tmpmatrix =
                        Matrix.CreateScale(parent_tween_size.Value.X, parent_tween_size.Value.Y, 0f) *
                          Matrix.CreateRotationZ(parent_tween_angle.Value) *
                          Matrix.CreateTranslation(parent_tween_position.Value.X, parent_tween_position.Value.Y, 0f)
                          ;
                }




                Vector2 tmpOrigin = new Vector2(0, bonetext.Bounds.Height / 2);

                bone_tween_position = Vector2.Transform(bone_tween_position, tmpmatrix);

                bone_tween_angle += parent_tween_angle.GetValueOrDefault(0);
                bone_tween_size = bone_tween_size *= parent_tween_size.GetValueOrDefault(new Vector2(1, 1));
                boneref.tween_rotation = bone_tween_angle;
                boneref.tween_scale = bone_tween_size;
                Vector2 tmpPosition = bone_tween_position;

                boneref.Transform =
                         Matrix.CreateScale(bone_tween_size.X, bone_tween_size.Y, 0f) *
                        Matrix.CreateRotationZ(bone_tween_angle) *

                        Matrix.CreateTranslation(bone_tween_position.X, bone_tween_position.Y, 0f)
                        ;
                float tmpRotation = bone_tween_angle * -1;

                Vector2 tmpScale = bone_tween_size;


                // spriteBatch.Draw(bonetext, tmpPosition, bonetext.Bounds, Color.White, tmpRotation, tmpOrigin, tmpScale, effects, 0);
                PrecalculateBones(spriteBatch, bone_tween_size, bone_tween_angle, bone_tween_position, boneref.BoneId);
            }
        }
        public Vector2 TransformPoint(Vector2 point, float Rotation, Vector2 Scale, Vector2 position)
        {
            Matrix m = Matrix.CreateScale(Scale.X, Scale.Y, 0f) *
                Matrix.CreateRotationZ(Rotation) *
                Matrix.CreateTranslation(position.X, position.Y, 0f);
            Vector2 result = Vector2.Transform(point, m);
            // Vector2 result = Vector2.Transform(point * Scale, Matrix.CreateRotationZ(Rotation));
            // result *= Scale;
            //result += position;
            return result;
        }
        public static float Distance(Vector2 A, Vector2 B)
        {
            return (float)Math.Sqrt(Math.Pow((A.X - B.X), 2) + Math.Pow((A.Y - B.Y), 2));
        }

        /// <summary>
        /// Draw the character
        /// </summary>
        /// <param name="spriteBatch">spritebatch to use for drawing</param>
        public void Draw(SpriteBatch spriteBatch)
        {
            if (Key1 == null)
            {
                return;
            }

            PrecalculateBones(spriteBatch, null, null, null, null);
            foreach (SpriterObjectTimelineKeyMapping obj1 in Key1.object_refs)
            {


                var tl = spriterData.entities[currentEntity].animations[currentSequence].Timelines.Single(x => x.TimelineId == obj1.TimelineId);
                {
                    var keyframe1 = tl.keys.LastOrDefault(x => x.KeyId <= obj1.KeyId);
                    if (keyframe1 == null) continue;

                    //if (spriterData.folders[keyframe1.KeyObject.FolderId].file[keyframe1.KeyObject.FileId].FileName == "p1_forearms_0/forearm_0.png")
                    {
                        var keyframe2 = tl.keys.FirstOrDefault(x => x.KeyId == keyframe1.KeyId + 1);
                        if (keyframe2 == null)
                        {
                            if (tl.keys.Last() == keyframe1)
                            {
                                keyframe2 = new SpriterTimelineKey();
                                keyframe2.KeyId = keyframe1.KeyId + 1;
                                keyframe2.KeyObject = keyframe1.KeyObject;
                                keyframe2.spin = keyframe1.spin;
                                keyframe2.time = spriterData.entities[currentEntity].animations[currentSequence].length;
                            }
                            else
                            {
                                throw new Exception("wtf");
                            }
                        }

                        //<key id="3" time="570">
                        //<object x="80"/>
                        //</key>
                        //<key id="4" time="742">
                        //<object x="89"/>
                        //</key>
                        //x=80+((89 – 80)*((604-570)/(742-570)))

                        int k1_t;
                        int k2_t;
                        float k1_x, k1_y, k1_w, k1_h, k1_a, k1_o, k1_origin_x, k1_origin_y;
                        float k2_x, k2_y, k2_w, k2_h, k2_a, k2_o, k2_origin_x, k2_origin_y;
                        bool k1_s, k2_s;

                        k1_t = keyframe1.time;
                        k2_t = keyframe2.time;
                        k1_x = keyframe1.KeyObject.Position.X;
                        k2_x = keyframe2.KeyObject.Position.X;
                        k1_y = keyframe1.KeyObject.Position.Y;
                        k2_y = keyframe2.KeyObject.Position.Y;
                        k1_w = keyframe1.KeyObject.scale.X;
                        k2_w = keyframe2.KeyObject.scale.X;
                        k1_h = keyframe1.KeyObject.scale.Y;
                        k2_h = keyframe2.KeyObject.scale.Y;
                        k1_origin_x = keyframe1.KeyObject.Pivot.X;
                        k1_origin_y = keyframe1.KeyObject.Pivot.Y;
                        k2_origin_x = keyframe2.KeyObject.Pivot.X;
                        k1_origin_y = keyframe2.KeyObject.Pivot.Y;
                        k1_a = DegreeToRadian(keyframe1.KeyObject.angle);
                        k2_a = DegreeToRadian(keyframe2.KeyObject.angle);
                        k1_o = keyframe1.KeyObject.opacity;
                        k2_o = keyframe2.KeyObject.opacity;
                        k1_s = (keyframe1.spin == 0 ? true : keyframe1.spin == 1 ? true : false);
                        k2_s = (keyframe2.spin == 0 ? true : keyframe2.spin == 1 ? true : false);


                        float tween_opacity = 1;//TODO
                        float time = ((float)(animationTime - k1_t) / ((k2_t - k1_t) == 0 ? 1 : (k2_t - k1_t)));
                        float tween_x = k1_x + ((k2_x - k1_x) * time);
                        float tween_y = k1_y + ((k2_y - k1_y) * time);
                        var angle1 = k1_a;
                        var angle2 = k2_a;

                        //If spin=”1” (counter-clockwise) and angleB - angleA is negative, then add 360 to
                        //angleB before using it in the above formula.
                        //If spin=”-1”, and angleB - angleA is positive, then subtract 360 from angleB
                        //before using it in the above formula.
                        if (k1_s && (angle2 - angle1 < 0))
                        {
                            angle2 += DegreeToRadian(360);
                        }

                        if (!k1_s && (angle2 - angle1 > 0))
                        {
                            angle2 -= DegreeToRadian(360);
                        }
                        float tween_angle = angle1 + ((angle2 - angle1) * time);
                        float tween_wdt = k1_w + ((k2_w - k1_w) * time);
                        float tween_hgt = k1_h + ((k2_h - k1_h) * time);
                        Vector2 tween_size = new Vector2(tween_wdt, tween_hgt);
                        Vector2 tween_position = new Vector2(tween_x, tween_y);


                        Rectangle rect = spriterData.ImageRectangles[keyframe1.KeyObject.FolderId][keyframe1.KeyObject.FileId];
                        Vector2 tmpOrigin = new Vector2(rect.Width * k1_origin_x, rect.Height * (1 - k1_origin_y));




                        tween_position -= tmpOrigin;
                        if (obj1.ParentIdSpecified)
                        {
                            applyParentTransform(obj1.ParentId, ref tween_position, ref tween_size, ref tween_angle, ref tween_opacity, tmpOrigin);

                        }
                        //tween_position += tmpOrigin;
                        //tmpOrigin = new Vector2(0, 0);

                        Matrix m = //Matrix.CreateScale(Scale.X, Scale.Y, 0f) *
                            //Matrix.CreateTranslation(-tmpOrigin.X, -tmpOrigin.Y, 0f) *
                                Matrix.CreateRotationZ(-this.Rotation); 
                                 // Matrix.CreateTranslation( tmpOrigin.X,tmpOrigin.Y, 0f);


                        tween_position = Vector2.Transform(tween_position , m);

                        //tween_position = TransformPoint(tween_position, this.Rotation, Vector2.One, Vector2.Zero);

                        Vector2 tmpPosition = this.Position;

                        tmpPosition.X += (tween_position.X) * (FlipX ? -1 : 1);
                        tmpPosition.Y += (tween_position.Y) * (FlipY ? -1 : 1) * -1;
                        float tmpRotation = tween_angle * -1;


                        tmpRotation += this.Rotation;
                        //if (FlipX != FlipY) { tmpRotation *= -1; }
                        Color Tint = Color.White;

                        //Tint.A = (byte)MathHelper.Clamp(0 - 20, 0, 255);
                        //Tint.A = (byte)(keyframe1.KeyObject.opacity);
                        if (spriterData.folders[keyframe1.KeyObject.FolderId].file[keyframe1.KeyObject.FileId].FileName == "path2985.png")
                        {
                            spriteBatch.DrawString(guiFont, string.Format("{0}", tween_angle), new Vector2(tmpPosition.X, tmpPosition.Y - 200), Tint);
                        }
                        spriteBatch.Draw(spriterData.texture, tmpPosition, spriterData.ImageRectangles[keyframe1.KeyObject.FolderId][keyframe1.KeyObject.FileId], Tint, tmpRotation, tmpOrigin, tween_size, effects, 0);
                    }
                }
            }
        }

        private void applyParentTransform(int parentid, ref Vector2 tween_position, ref Vector2 tween_size, ref float tween_angle, ref float tween_opacity, Vector2 offset)
        {
            var boneref = Key1.bone_refs.SingleOrDefault(x => x.BoneId == parentid);
            if (boneref == null)
            {
                return;
                //WTF
            }
            tween_position = Vector2.Transform(tween_position + offset, boneref.Transform);
            tween_angle += boneref.tween_rotation;
            tween_size *= boneref.tween_scale;
        }


        private float DegreeToRadian(float angle)
        {
            return (float)Math.PI * angle / 180;
        }
        private double RadianToDegree(double angle)
        {
            return angle * (180.0 / Math.PI);
        }
    }
}
